/*
 * Copyright (c) 2022 Winner Microelectronics Co., Ltd. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*****************************************************************************
*
* File Name : wm_adc.c
*
* Description: adc Driver Module
*
* Copyright (c) 2014 Winner Microelectronics Co., Ltd.
* All rights reserved.
*
* Author : dave
*
* Date : 2014-8-15
*****************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "wm_regs.h"
#include "wm_dma.h"
#include "wm_io.h"
#include "wm_irq.h"
#include "wm_mem.h"
#include "wm_adc.h"

#define HR_SD_ADC_CONFIG_REG 0
static int adc_offset = 0;
static int *adc_dma_buffer = NULL;
volatile ST_ADC gst_adc;

ATTRIBUTE_ISR void ADC_IRQHandler(void)
{
    u32 adcvalue;
    int reg;
    csi_kernel_intrpt_enter();

    reg = tls_reg_read32(HR_SD_ADC_INT_STATUS);
    if (reg & ADC_INT_MASK) {     // ADC中断
        tls_adc_clear_irq(ADC_INT_TYPE_ADC);

        if (gst_adc.adc_cb) {
            adcvalue = tls_read_adc_result();
            gst_adc.adc_cb((int *)&adcvalue, 1);
        }
    }
    if (reg & CMP_INT_MASK) {
        tls_adc_clear_irq(ADC_INT_TYPE_ADC_COMP);
        if (gst_adc.adc_bigger_cb)
            gst_adc.adc_bigger_cb(NULL, 0);
    }
    csi_kernel_intrpt_exit();
}

static void adc_dma_isr_callbk(void)
{
    if (gst_adc.adc_dma_cb) {
        if (adc_dma_buffer) {
            gst_adc.adc_dma_cb((int *)(adc_dma_buffer), gst_adc.valuelen);
        }
    }
}

void tls_adc_init(u8 ifusedma, u8 dmachannel)
{
    tls_reg_write32(HR_SD_ADC_CTRL, ANALOG_SWITCH_TIME_VAL(0x50)|ANALOG_INIT_TIME_VAL(0x50)|ADC_IRQ_EN_VAL(0x1));
    tls_irq_enable(ADC_IRQn);

    // 注册中断和channel有关，所以需要先请求
    if (ifusedma) {
        gst_adc.dmachannel = tls_dma_request(dmachannel, TLS_DMA_FLAGS_CHANNEL_SEL(TLS_DMA_SEL_SDADC_CH0 + dmachannel) |
                            TLS_DMA_FLAGS_HARD_MODE);    // 请求dma，不要直接指定，因为请求的dma可能会被别的任务使用
        if (gst_adc.dmachannel != 0xFF) {
            tls_dma_irq_register(gst_adc.dmachannel, (void(*)(void*))adc_dma_isr_callbk,
                                 NULL, TLS_DMA_IRQ_TRANSFER_DONE);
        }
    }
}

void tls_adc_clear_irq(int inttype)
{
    int reg;
    reg = tls_reg_read32(HR_SD_ADC_INT_STATUS);
    if (ADC_INT_TYPE_ADC == inttype) {
        reg |= ADC_INT_MASK;
        tls_reg_write32(HR_SD_ADC_INT_STATUS, reg);
    } else if (ADC_INT_TYPE_ADC_COMP== inttype) {
        reg |= CMP_INT_MASK;
        tls_reg_write32(HR_SD_ADC_INT_STATUS, reg);
    } else if (ADC_INT_TYPE_DMA == inttype) {
        tls_dma_irq_clr(gst_adc.dmachannel, TLS_DMA_IRQ_TRANSFER_DONE);
    }
}

void tls_adc_irq_register(int inttype, void (*callback)(int *buf, u16 len))
{
    if (ADC_INT_TYPE_ADC == inttype) {
        gst_adc.adc_cb = callback;
    } else if (ADC_INT_TYPE_DMA == inttype) {
        gst_adc.adc_dma_cb = callback;
    } else if (ADC_INT_TYPE_ADC_COMP == inttype) {
        gst_adc.adc_bigger_cb = callback;
    }
}

u32 tls_read_adc_result(void)
{
    u32 value;
    u32 ret;

    value = tls_reg_read32(HR_SD_ADC_RESULT_REG);
    ret = ADC_RESULT_VAL(value);

    return ret;
}

void tls_adc_start_with_cpu(int Channel)
{
    u32 value;

    /* Stop adc first */
    value = tls_reg_read32(HR_SD_ADC_ANA_CTRL);
    value |= CONFIG_PD_ADC_VAL(1);
    value &= ~(CONFIG_RSTN_ADC_VAL(1)|CONFIG_EN_LDO_ADC_VAL(1));
    value &= ~(CONFIG_ADC_CHL_SEL_MASK);
    value |= CONFIG_ADC_CHL_SEL(Channel);

    tls_reg_write32(HR_SD_ADC_ANA_CTRL, value);

    value = tls_reg_read32(HR_SD_ADC_ANA_CTRL);
    value &= ~(CONFIG_PD_ADC_VAL(1));
    value |= (CONFIG_RSTN_ADC_VAL(1)|CONFIG_EN_LDO_ADC_VAL(1));
    tls_reg_write32(HR_SD_ADC_ANA_CTRL, value);
}

void tls_adc_start_with_dma(int Channel, int Length)
{
    u32 value;
    int len;
    int channel = Channel;

    if (channel < 0 || channel > 11) { // 11:byte alignment
        return;
    }

    if (Length > ADC_DEST_BUFFER_SIZE)
        len = ADC_DEST_BUFFER_SIZE;
    else
        len = Length;

    gst_adc.valuelen = len;

    if (adc_dma_buffer) {
        tls_mem_free(adc_dma_buffer);
        adc_dma_buffer = NULL;
    }

    adc_dma_buffer = tls_mem_alloc(len * 4); // 4:byte alignment
    if (adc_dma_buffer == NULL) {
        wm_printf("adc dma buffer alloc failed\r\n");
        return;
    }

    channel &= 0xF;

    /* disable adc:set adc pd, rstn and ldo disable */
    value = tls_reg_read32(HR_SD_ADC_ANA_CTRL);
    value |= CONFIG_PD_ADC_VAL(1);
    value &= ~(CONFIG_RSTN_ADC_VAL(1)|CONFIG_EN_LDO_ADC_VAL(1));
    tls_reg_write32(HR_SD_ADC_ANA_CTRL, value);

    /* Stop dma if necessary */
    while (DMA_CHNLCTRL_REG(gst_adc.dmachannel) & 1) {
        DMA_CHNLCTRL_REG(gst_adc.dmachannel) = 2; // 2:byte alignment
    }

    DMA_SRCADDR_REG(gst_adc.dmachannel) = HR_SD_ADC_RESULT_REG;
    DMA_DESTADDR_REG(gst_adc.dmachannel) = adc_dma_buffer;
    DMA_SRCWRAPADDR_REG(gst_adc.dmachannel) = HR_SD_ADC_RESULT_REG;
    DMA_DESTWRAPADDR_REG(gst_adc.dmachannel) = adc_dma_buffer;
    DMA_WRAPSIZE_REG(gst_adc.dmachannel) = (len * 4) << 16;

    /* Dest_add_inc, halfword,  */
    DMA_CTRL_REG(gst_adc.dmachannel) = (3 << 3)|(2 << 5)|((len * 4) << 8)|(1 << 0);
    DMA_INTMASK_REG &= ~(0x01 << (gst_adc.dmachannel *2 + 1));
    DMA_CHNLCTRL_REG(gst_adc.dmachannel) = 1;        /* Enable dma */

    /* Enable dma */
    value = tls_reg_read32(HR_SD_ADC_CTRL);
    value |= (1 << 0);
    tls_reg_write32(HR_SD_ADC_CTRL, value);

    value = tls_reg_read32(HR_SD_ADC_ANA_CTRL);
    value &= ~(CONFIG_ADC_CHL_SEL_MASK);
    value |= CONFIG_ADC_CHL_SEL(channel);
    value &= ~(CONFIG_PD_ADC_VAL(1));
    value |= (CONFIG_RSTN_ADC_VAL(1)|CONFIG_EN_LDO_ADC_VAL(1));
    tls_reg_write32(HR_SD_ADC_ANA_CTRL, value);        /* start adc */
}

void tls_adc_stop(int ifusedma)
{
    u32 value;

    tls_reg_write32(HR_SD_ADC_PGA_CTRL, 0);

    value = tls_reg_read32(HR_SD_ADC_ANA_CTRL);
    value |= CONFIG_PD_ADC_VAL(1);
    value &= ~(CONFIG_RSTN_ADC_VAL(1)|CONFIG_EN_LDO_ADC_VAL(1));
    tls_reg_write32(HR_SD_ADC_ANA_CTRL, value);

    /* Disable dma */
    value = tls_reg_read32(HR_SD_ADC_CTRL);
    value &= ~(1<<0);
    tls_reg_write32(HR_SD_ADC_CTRL, value);

    /* Disable compare function and compare irq */
    value = tls_reg_read32(HR_SD_ADC_CTRL);
    value &= ~(3<<4);
    tls_reg_write32(HR_SD_ADC_CTRL, value);

    if (ifusedma)
        tls_dma_free(gst_adc.dmachannel);

    if (adc_dma_buffer) {
        tls_mem_free(adc_dma_buffer);
        adc_dma_buffer = NULL;
    }
}

void tls_adc_config_cmp_reg(int cmp_data, int cmp_pol)
{
    u32 value;

    tls_reg_write32(HR_SD_ADC_CMP_VALUE, CONFIG_ADC_INPUT_CMP_VAL(cmp_data));

    value = tls_reg_read32(HR_SD_ADC_CTRL);
    if (cmp_pol) {
        value |= CMP_POLAR_MASK;
    } else {
        value &= ~CMP_POLAR_MASK;
    }
    tls_reg_write32(HR_SD_ADC_CTRL, value);
}

void tls_adc_cmp_start(int Channel, int cmp_data, int cmp_pol)
{
    u32 value;

    /* Stop adc first */
    value = tls_reg_read32(HR_SD_ADC_ANA_CTRL);
    value |= CONFIG_PD_ADC_VAL(1);
    value &= ~(CONFIG_RSTN_ADC_VAL(1)|CONFIG_EN_LDO_ADC_VAL(1));
    value |= CONFIG_ADC_CHL_SEL(Channel);
    tls_reg_write32(HR_SD_ADC_ANA_CTRL, value);

    tls_adc_config_cmp_reg(cmp_data, cmp_pol);

    value = tls_reg_read32(HR_SD_ADC_ANA_CTRL);
    value &= ~(CONFIG_PD_ADC_VAL(1));
    value |= (CONFIG_RSTN_ADC_VAL(1)|CONFIG_EN_LDO_ADC_VAL(1));
    tls_reg_write32(HR_SD_ADC_ANA_CTRL, value);     /* start adc */

    /* Enable compare function and compare irq */
    value = tls_reg_read32(HR_SD_ADC_CTRL);
    value |= (3<<4);
    tls_reg_write32(HR_SD_ADC_CTRL, value);
}

void tls_adc_reference_sel(int ref)
{
    u32 value;

    value = tls_reg_read32(HR_SD_ADC_PGA_CTRL);
    if (ADC_REFERENCE_EXTERNAL == ref) {
        value |= BYPASS_INNER_REF_SEL;
    } else if (ADC_REFERENCE_INTERNAL == ref) {
        value &= ~BYPASS_INNER_REF_SEL;
    }
    tls_reg_write32(HR_SD_ADC_PGA_CTRL, value);
}

void tls_adc_set_clk(int div)
{
    u32 value;

    value = tls_reg_read32(HR_CLK_SEL_CTL);
    value &= ~(0xFF<<8);
    value |=  (div&0xFF)<<8;
    tls_reg_write32(HR_CLK_SEL_CTL, value);
    value = tls_reg_read32(HR_CLK_DIV_CTL);
    value |= (1 << 31);
    tls_reg_write32(HR_CLK_DIV_CTL, value);
}

void tls_adc_set_pga(int gain1, int gain2)
{
    u32 val = 0;
    u8 gain1times = 0;
    u8 gain2times = 0;
    switch (gain1) {
        case 1:
            gain1times = 0;
            break;
        case 16:
            gain1times = 1;
            break;
        case 32:
            gain1times = 2;
            break;
        case 64:
            gain1times = 3;
            break;
        case 128:
            gain1times = 4;
            break;
        case 256:
            gain1times = 5;
            break;
        default:
            gain1times = 0;
            break;
    }

    switch (gain2) {
        case 1:
            gain2times = 0;
            break;
        case 2:
            gain2times = 1;
            break;
        case 3:
            gain2times = 2;
            break;
        case 4:
            gain2times = 3;
            break;
        default:
            gain2times = 0;
            break;
    }

    val = GAIN_CTRL_PGA_VAL(gain2times)|CLK_CHOP_SEL_PGA_VAL(gain1times)|
        PGA_BYPASS_VAL(0)|PGA_CHOP_ENP_VAL(1)|PGA_EN_VAL(1);
    tls_reg_write32(HR_SD_ADC_PGA_CTRL, val);
}

void signedToUnsignedData(int *adcValue)
{
    if (*adcValue &0x20000) {
        *adcValue = *adcValue &0x1FFFF;
    } else {
        *adcValue = *adcValue |0x20000;
    }
}

static void waitForAdcDone(void)
{
    u32 counter = 0;
    u32 timeout = 10000;
    u32 reg = 0;

    /* wait for transfer success */
    tls_irq_disable(ADC_IRQn);
    while (timeout--) {
        reg = tls_reg_read32(HR_SD_ADC_INT_STATUS);
        if (reg & ADC_INT_MASK) {
            counter++;
            tls_reg_write32(HR_SD_ADC_INT_STATUS, reg|ADC_INT_MASK);
            if (counter == 4) { // 4:byte alignment
                break;
            }
        } else if (reg & CMP_INT_MASK) {
            counter++;
            tls_reg_write32(HR_SD_ADC_INT_STATUS, reg|CMP_INT_MASK);
            if (counter == 4) { // 4:byte alignment
                break;
            }
        }
    }
    tls_irq_enable(ADC_IRQn);
}

u32 adc_get_offset(void)
{
    adc_offset = 0;
    tls_adc_init(0, 0);
    tls_adc_reference_sel(ADC_REFERENCE_INTERNAL);
    tls_adc_start_with_cpu(CONFIG_ADC_CHL_OFFSET);
    tls_adc_set_pga(1, 1);
    tls_adc_set_clk(0x28);

    waitForAdcDone();
    adc_offset = tls_read_adc_result(); // 获取adc转换结果
    signedToUnsignedData(&adc_offset);
    tls_adc_stop(0);

    return adc_offset;
}

int adc_get_interTemp(void)
{
    return adc_temp();
}

int adc_get_inputVolt(u8 channel)
{
    int average = 0;
    double voltage = 0.0;

    adc_get_offset();

    tls_adc_init(0, 0);
    tls_adc_reference_sel(ADC_REFERENCE_INTERNAL);
    tls_adc_set_pga(1, 1);
    tls_adc_set_clk(0x28);
    tls_adc_start_with_cpu(channel);

    waitForAdcDone();
    average = tls_read_adc_result();
    signedToUnsignedData(&average);
    tls_adc_stop(0);

    if ((channel == 8) || (channel == 9)) {
        voltage = ((double)average - (double)adc_offset)/4.0;
        voltage = voltage*(126363/1000)/1000000;
    } else {
        voltage = ((double)average - (double)adc_offset)/4.0;
        voltage = 1.196 + voltage*(126363/1000.0)/1000000;
    }

    average = (int)(voltage * 1000); // 1000:byte alignment
    return average;
}

u32 adc_get_interVolt(void)
{
    u32 voltValue;
    u32 value = 0;
    adc_get_offset();

    tls_adc_init(0, 0);
    tls_adc_reference_sel(ADC_REFERENCE_INTERNAL);
    tls_adc_set_pga(1, 1);
    tls_adc_set_clk(0x28);
    tls_adc_start_with_cpu(CONFIG_ADC_CHL_VOLT);
    voltValue = 0;
    for (int i = 0;i < 10; i++) { // 10:loop cap
        waitForAdcDone();
        value = tls_read_adc_result();
        signedToUnsignedData(&value);
        voltValue += value;
    }
    voltValue = voltValue / 10; // 10:byte alignment
    tls_adc_stop(0);
    voltValue =
        ((voltValue - adc_offset) * 685 / 20 + 1200000) * 2; // 685:byte alignment, 20:byte alignment, 2:byte alignment
    value = voltValue - voltValue * 10 / 100; // 10:byte alignment, 100:byte alignment

    return value / 1000; // 1000:byte alignment
}

/**
 * @brief          This function is used to get chip's internal work temperature
 *
 * @return         chip temperature, unit: 1/1000 degree
 *
 * @note           Only use to get chip's internal work temperature.
 */
int adc_temp(void)
{
    u32 code1 = 0, code2 = 0;
    u32 val = 0;
    int temperature = 0;

    tls_adc_init(0, 0);
    tls_adc_reference_sel(ADC_REFERENCE_INTERNAL);
    tls_adc_set_pga(1, 4); // 4:value of gain2
    tls_adc_start_with_cpu(CONFIG_ADC_CHL_TEMP);
    tls_adc_set_clk(0x28);
    val = tls_reg_read32(HR_SD_ADC_TEMP_CTRL);
    val &= ~TEMP_GAIN_MASK;
    val |= TEMP_GAIN_VAL(0);
    val |= TEMP_EN_VAL(1);

    val &= (~(TEMP_CAL_OFFSET_MASK));
    tls_reg_write32(HR_SD_ADC_TEMP_CTRL, val);
    waitForAdcDone();
    code1 = tls_read_adc_result();
    signedToUnsignedData((int *)&code1);

    val |= TEMP_CAL_OFFSET_MASK;
    tls_reg_write32(HR_SD_ADC_TEMP_CTRL, val);
    waitForAdcDone();
    code2 = tls_read_adc_result();
    signedToUnsignedData((int *)&code2);

    val &= ~(TEMP_EN_VAL(1));
    tls_reg_write32(HR_SD_ADC_TEMP_CTRL, val);

    tls_adc_stop(0);

    temperature = ((int)code1 - (int)code2);

    temperature = ((temperature * 1000 / (int)(2 * 2 * 4) - // 1000:byte alignment, 2:byte alignment, 4:byte alignment
        4120702) * 1000 / 15548); // 4120702:byte alignment, 1000:byte alignment, 15548:byte alignment

    return temperature;
}